﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.Globalization;

namespace BC2CitadelDaemon
{
    public delegate void NewMessage(object sender, MessageEventArgs e);
    public delegate void PacketRecievedEvent(object sender, PacketEventArgs e);

    public class PacketEventArgs
    {
        public Packet packet = new Packet();

        public PacketEventArgs(Packet aPacket)
        {
            packet = aPacket;
        }
    }

    public class MessageEventArgs
    {
        public string message = string.Empty;

        public MessageEventArgs(string aMessage)
        {
            message = aMessage;
        }
    }

    public class Server
    {
        private string address;
        private int port;
        private string password;
        Socket server;
        byte[] data = new byte[128];
        int size = 128;
        int sequence = 0;

        bool loggedIn = false;

        Dictionary<int, RequestType> requests = new Dictionary<int, RequestType>();

        public event NewMessage MsgEvent;
        public event PacketRecievedEvent PacketRecieved;

        public bool LoggedIn
        {
            get { return loggedIn; }
        }

        public Server(string aAddress, int aPort, string aPassword)
        {
            address = aAddress;
            port = aPort;
            password = aPassword;
        }

        public void Login()
        {
            Packet temp = new Packet();
            temp.Sequence = sequence;
            temp.CommandString = "login.hashed";
            temp.IsFromServer = true;
            requests.Add(sequence, RequestType.Login);

            Send(temp.Encode());
        }

        public void ReturnLogin(string salt)
        {
            List<byte> blist = new List<byte>(); // Collection to store our varioys bytes

            // Parse through the salt to get the int ascii codes which allow us to convert to
            // the proper byte values.  Other automated conversions screw this up.  To see the
            // proper salt string you must use Encoding Page 437.
            for (int i = 0; i < salt.Length / 2; i++)
            {
                int code = Convert.ToInt32(salt.Substring(i * 2, 2), 16);
                blist.Add((byte)code);
            }

            // Convert password to byte values.
            blist.AddRange(Encoding.Default.GetBytes(password));

            // Create MD5 Provider and create a Hash from our list of bytes.
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] hash = md5.ComputeHash(blist.ToArray());

            // Create a new packet to encode the proper info for sending.
            Packet temp = new Packet();
            temp.Sequence = sequence;
            temp.CommandString = "login.hashed " + BitConverter.ToString(hash).Replace("-",""); // We must strip out the dashes
            temp.IsFromServer = true;

            // Just so we can see what we are sending temporarily
            PrintPacket(temp);

            // Send the encoded packet to the server.
            requests.Add(sequence, RequestType.LoginHash);
            Send(temp.Encode());
        }
            


        #region Socket
        protected virtual void PrintMessage(string message)
        {
            if (MsgEvent != null)
            {
                
                MsgEvent(this, new MessageEventArgs(message));
            }
        }

        public void CreateSocket()
        {

            PrintMessage("Attempting To Connect To: " + address + ":" + port.ToString());
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(address), port);
            socket.BeginConnect(endPoint, new AsyncCallback(Connected), socket);

        }

        void Connected(IAsyncResult iar)
        {
            server = (Socket)iar.AsyncState;

            try
            {
                server.EndConnect(iar);
                PrintMessage("Connection Established!");
                //server.BeginReceive(data, 0, size, SocketFlags.None, new AsyncCallback(ReceiveData), server);
            }
            catch (SocketException error)
            {
                PrintMessage("Error Connecting: " + error.Message);
            }
        }

        private void PrintPacket(Packet packet)
        {
            string decode = string.Empty;

            decode += "SEQ: " + packet.Sequence.ToString() + " ";
            decode += "IFS: " + packet.IsFromServer.ToString() + " ";
            decode += "IR: " + packet.IsResponse.ToString() + " ";

            foreach (string word in packet.Words)
            {
                decode += word + " ";
            }

            PrintMessage(decode);
        }

        void ReceiveData(IAsyncResult iar)
        {
            Socket remote = (Socket)iar.AsyncState;
            int recv = remote.EndReceive(iar);

            Packet temp = new Packet();
            temp.Decode(data);

            if (!temp.IsFromServer)
            {
                PrintMessage("EVENT RECIEVED");
                PrintPacket(temp);
            }
            else
            {
                PrintMessage("RESPONSE RECIEVED");
                PrintPacket(temp);

                if (requests.ContainsKey(temp.Sequence))
                {
                    switch (requests[temp.Sequence])
                    {
                        case RequestType.Login:
                            ReturnLogin(temp.Words[1]);
                            requests.Remove(temp.Sequence);
                            break;
                        case RequestType.LoginHash:
                            if (temp.Words[0] == "OK")
                            {
                                loggedIn = true;
                                //LoginEvents();
                            }
                            else
                            {
                                loggedIn = false;
                                //LoginFailed();
                            }
                            break;          
                    }
                }

            }

            if (PacketRecieved != null)
            {
                PacketRecieved(this, new PacketEventArgs(temp));
            }

            

            //PrintMessage(BitConverter.ToString(data));
        }

        public void WaitForData()
        {
        }

        public void Send(byte[] packet)
        {
            PrintMessage("SENDING > " + BitConverter.ToString(packet));
            server.BeginSend(packet, 0, packet.Length, SocketFlags.None, new AsyncCallback(SendData), server);
        }

        void SendData(IAsyncResult iar)
        {
            
            Socket remote = (Socket)iar.AsyncState;
            int sent = remote.EndSend(iar);
            remote.BeginReceive(data, 0, size, SocketFlags.None, new AsyncCallback(ReceiveData), remote);
            sequence++;
        }

        #endregion



    }
}

